<template>
  <el-input
    ref="elInputRef"
    v-bind="props"
    v-model="baseData.newValue"
    type="url"
    @clear="emit('clear')"
    @blur="blurEvent"
    @input="inputEvent"
    @focus="emit('focus', $event)"
    @change="emit('change', $event)"
  >
    <template v-if="$slots.prefix" #prefix>
      <slot name="prefix"></slot>
    </template>
    <template v-if="$slots.suffix" #suffix>
      <slot name="suffix"></slot>
    </template>
    <template v-if="$slots.prepend" #prepend>
      <slot name="prepend"></slot>
    </template>
    <template v-if="$slots.append" #append>
      <slot name="append"></slot>
    </template>
  </el-input>
</template>

<script setup lang="ts">
  import { inputProps } from 'element-plus';
  import { organizeDigital } from '@/utils/numb';
  import { type $InputVerify, verifyNumbInput } from '@/utils/verify-number';
  import type { PropType } from 'vue';

  const props = defineProps({
    ...inputProps,
    /** 强制默认值(当为空时强制设置为此值 (注意可能显示的为空,但实际返回的是forceValue)) */
    forceValue: { type: Number, default: undefined },
    /** 保留多少位小数 */
    decimals: { type: Number, default: undefined },
    /** 验证的类型 */
    verify: { type: String as PropType<$InputVerify>, required: true },
    /** 最小值 */
    min: { type: Number, default: undefined },
    /** 最大值 */
    max: { type: Number, default: undefined },
  });

  defineSlots<{
    /** 输入框头部内容 (输入框里面)，只对非 type="textarea" 有效 */
    prefix: () => void;
    /** 输入框尾部内容 (输入框里面)，只对非 type="textarea" 有效 */
    suffix?: () => void;
    /** 输入框前置内容 (输入框外面)，只对非 type="textarea" 有效 */
    prepend?: () => void;
    /** 输入框后置内容 (输入框外面)，只对非 type="textarea" 有效 */
    append?: () => void;
  }>();

  const emit = defineEmits<{
    (e: 'update:modelValue', val: number | string): void;
    (e: 'blur', event: FocusEvent): void;
    (e: 'focus', event: FocusEvent): void;
    (e: 'change', value: number | string): void;
    (e: 'input', value: number | string, original: number | string): void;
    (e: 'clear'): void;
  }>();

  const elInputRef: $RefComp<'ElInput'> = ref(null);

  const baseData = shallowReactive({
    newValue: props.modelValue || '',
  });
  const isVerify = computed(() => Boolean(props.verify));

  watch(
    () => props.modelValue,
    (value, oldValue) => {
      const old = ['number', 'string'].includes(typeof oldValue) ? oldValue : '';
      if (value !== null && value !== undefined) {
        String(value) !== String(oldValue) && updateNumb(value, old || '');
      } else {
        baseData.newValue = '';
      }
    },
    { immediate: true }
  );

  watch(
    () => baseData.newValue,
    (val) => {
      if (props.forceValue !== undefined && val === '') {
        emit('update:modelValue', props.forceValue);
      } else {
        emit('update:modelValue', val);
      }
    }
  );

  /**
   * 失去焦点时触发
   */
  function blurEvent(event: FocusEvent) {
    if (props.verify && baseData.newValue !== '') {
      const newValue = Number(baseData.newValue) || 0;
      updateNumb(newValue, newValue);
    }
    emit('blur', event);
  }

  /**
   * 输入改变时触发
   */
  function inputEvent(value: number | string) {
    const newVal = String(value).replace('。', '.');
    updateNumb(newVal, baseData.newValue);
    nextTick(() => {
      emit('input', baseData.newValue, value);
    });
  }

  /**
   * 更新数字
   */
  function updateNumb(value: number | string, oldValue: number | string) {
    if (isVerify.value && value !== '') {
      baseData.newValue = getFinalValue(value, oldValue);
    } else if (props.forceValue !== undefined && value === '') {
      baseData.newValue = props.forceValue;
    } else {
      baseData.newValue = value;
    }
  }

  /**
   * 得到验证后的最终值
   */
  function getFinalValue(value: number | string, old: number | string) {
    const oldValue = ['number', 'string'].includes(typeof old) ? old : '';
    let newValue = value;
    if (newValue !== '') {
      const verify: $InputVerify = props.verify || 'all_float';
      newValue = verifyNumbInput(verify, newValue, oldValue);
      if (!isNaN(Number(newValue)) && !String(newValue).endsWith('.')) {
        newValue = verifyNumbSize(newValue, props.max, props.min);
        const srtVal = String(newValue);
        if (typeof props.decimals === 'number' && props.decimals >= 0 && srtVal.indexOf('.') > 0 && String(Number(newValue)) === srtVal) {
          newValue = organizeDigital(Number(newValue), props.decimals);
        }
      }
    }
    return String(Number(newValue)) === String(newValue) ? Number(newValue) : newValue;
  }

  /**
   * 处理最大最小值 (当超过最大最小值时用最大最小值代替)
   */
  function verifyNumbSize(numb: number | string, maxNumb?: number, minNumb?: number) {
    const newNumb = Number(numb);
    if (!String(newNumb).endsWith('.') && !isNaN(newNumb)) {
      if (typeof maxNumb === 'number' && newNumb > maxNumb) {
        return maxNumb;
      } else if (typeof minNumb === 'number' && newNumb < minNumb) {
        return minNumb;
      }
    }
    // 注意一定要返回numb 而不是 newNumb，j解决 0.01 问题
    return numb;
  }

  defineExpose({
    focus: () => elInputRef.value?.focus(),
  });
</script>
